依赖注入框架 -- Dagger2 进阶

前言

Dagger2 基础分析了 @Inject,@Component,@Module,@Provides 是如何构成 dagger2 整个依赖注入框架的。

主线已经完成,dagger2 中剩下的 @Qualifier(限定符), @Singleton(单例),@Scope(作用域)是对整个依赖注入框架细节上完善,提供更完善的功能。

@Qualifier

Dagger2 基础中分析到 @Component 是一个注入器,起着桥梁的作用。被依赖类的实例有两种创建方式:

  • @Inject 标注构造函数创建
  • @Module 中工厂模式创建

这两种方式是有优先级之分的,Component会首先从Module中查找实例,找不到才会去查找Inject方式创建的实例。
问题随之而来,在同一种实例创建方式中,可能有多个方法创建类示例,注入器应该选择哪个? @Qualifier 的作用就是用来决定 Component 做选择的。

我们使用 @Qualifier 来定义自己的注解,然后通过自定义注解去标注依赖的方法和依赖需求方,这样 Dagger2 就知道为谁提供依赖了。

Component 组织方式

@Scope, @Singleton 有些坑,结合Component讲解,个人认为会更要效果。

如何划分Component

如果一个 app 只有一个Component,脑补一下吧,简直是世界末日。这个Component会很难维护,变化率极高,很庞大,职责不明确。所以我们要将Component进行划分

  • 要有一个全局的Component,定义为 ApplicationComponent,负责管理整个 app 的全局类示例(整个app都要用到的类的实例,比如applicationContext,这些类都是单例的)
  • 每一个页面对应一个Component,比如一个Activity或者Fragment页面定义一个Component,当然这个不是必须的,某些页面依赖的类是一样的,可以共用一个Component。

@Singleton仅起标注作用

前面提到 ApplicationComponent 负责管理整个app用到的全局类示例,如何才能创建单例?

  • Module 中定义创建全局类实例
  • ApplicationComponent 管理Module
  • 保证ApplicationComponent只有一个实例(在app的Application中实例化)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static AppComponent getAppComponent(){
    if (appComponent == null) {
    appComponent = DaggerAppComponent.builder()
    .appModule(new AppModule(instance))
    .httpModule(new HttpModule())
    .build();
    }
    return appComponent;
    }

Dagger2 中真正创建单例的方法是上面的不走,全局类实例的生命周期就和Application一致了。疑问又来了,@Singleton 岂不是多余的? 答案当然是NO!!! @Singleton有以下作用:

  • 更好的管理 ApplicationComponent 和 Module 之间的关系,保证 ApplicationComponent 和 Module是匹配的。若两者的作用域不一样,则在编译时报错。
  • 提高代码可读性,让猿们清晰的看到 Module中创建的类实例是单例。

组织Component

我们划分好Component之后,全局类实例也已经创建了单例模式,如果其他的Component想要把全局的类实例注入到目标类中,怎么办? 我们需要组织Component之间的关系。 具体的组织方式有以下2种:

  • 依赖方式
    一个Component是依赖于一个或多个Component,使用dependencies属性来添加依赖的Component
  • 包含方式
    一个 Component 包含一个或多个Component,被包含的Component还可以继续包含其他的Component。 使用 @Subcomponent 注解组织Component

@Scope的真正用处

@Scope的真正用处是用于组织Component

  • 为了更好的管理Component,不管是依赖还是包含,都有必要用自定义的Scope标注,使用不同的自定义Scope来提现Component之间的组织方式。编译器会检查有依赖关系或包含关系的Component,如果没有自定义Scope标注,会报错。
  • 更好的管理Component与Module之间的匹配关系,编译器会检查Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类示例方法的注解不一样,报错。
  • 可读性提高,比如用Singleton标注全局类,这样我们可以立刻明白这类是全局单例类。

Dagger原理分析

Module

我们的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
public class ActivityModule {
private Activity mActivity;

public ActivityModule(Activity activity) {
this.mActivity = activity;
}

@Provides
@ActivityScope
public Activity provideActivity() {
return mActivity;
}
}

生成的工厂类代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ActivityModule_ProvideActivityFactory implements Factory<Activity> {
private final ActivityModule module;

public ActivityModule_ProvideActivityFactory(ActivityModule module) {
assert module != null;
this.module = module;
}

@Override
public Activity get() {
Activity provided = module.provideActivity();
if (provided == null) {
throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
}
return provided;
}

public static Factory<Activity> create(ActivityModule module) {
return new ActivityModule_ProvideActivityFactory(module);
}
}

  • ActivityModule_ProvideActivityFactory 中的 get() 方法调用了 ActivityModule 中的 provideActivity() 方法来获取我们需要的依赖 provideActivity 对象。
  • ActivityModule_ProvideActivityFactory 的对象是由 create() 方法创建,并且传入 ActivityModule 对象。

那么一定有地方调用了 create() 方法创建 Factory 对象,并通过 get 方法获取实例。

之前多次说到 Component 是依赖提供方和依赖需求方之间的桥梁,下面我们分析一下,Dagger2 是如何通过 Component 将两者联系起来的。

Component

我们的代码

1
2
3
4
5
6
7
@ActivityScope
@Component(dependencies = AppComponent.class,modules = ActivityModule.class)
public interface ActivityComponent {
Activity getActivity();

void inject(RepositoryActivity repositoryActivity);
}

生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerActivityComponent implements ActivityComponent {
private Provider<Activity> provideActivityProvider;
private Provider<RetrofitHelper> getRetrofitHelperProvider;
private Provider<RepositoryPresenter> repositoryPresenterProvider;
private MembersInjector<BaseActivity<RepositoryPresenter>> baseActivityMembersInjector;
private MembersInjector<RepositoryActivity> repositoryActivityMembersInjector;

private DaggerActivityComponent(Builder builder) {
assert builder != null;
initialize(builder);
}

public static Builder builder() {
return new Builder();
}

private void initialize(final Builder builder) {
this.provideActivityProvider = ScopedProvider.create(ActivityModule_ProvideActivityFactory.create(builder.activityModule));
this.getRetrofitHelperProvider = new Factory<RetrofitHelper>() {
private final AppComponent appComponent = builder.appComponent;
@Override public RetrofitHelper get() {
RetrofitHelper provided = appComponent.getRetrofitHelper();
if (provided == null) {
throw new NullPointerException("Cannot return null from a non-@Nullable component method");
}
return provided;
}
};
this.repositoryPresenterProvider = RepositoryPresenter_Factory.create((MembersInjector) MembersInjectors.noOp(), getRetrofitHelperProvider);
this.baseActivityMembersInjector = BaseActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), repositoryPresenterProvider);
this.repositoryActivityMembersInjector = MembersInjectors.delegatingTo(baseActivityMembersInjector);
}

@Override
public Activity getActivity() {
return provideActivityProvider.get();
}

@Override
public void inject(RepositoryActivity repositoryActivity) {
repositoryActivityMembersInjector.injectMembers(repositoryActivity);
}

public static final class Builder {
private ActivityModule activityModule;
private AppComponent appComponent;

private Builder() {
}

public ActivityComponent build() {
if (activityModule == null) {
throw new IllegalStateException("activityModule must be set");
}
if (appComponent == null) {
throw new IllegalStateException("appComponent must be set");
}
return new DaggerActivityComponent(this);
}

public Builder activityModule(ActivityModule activityModule) {
if (activityModule == null) {
throw new NullPointerException("activityModule");
}
this.activityModule = activityModule;
return this;
}

public Builder appComponent(AppComponent appComponent) {
if (appComponent == null) {
throw new NullPointerException("appComponent");
}
this.appComponent = appComponent;
return this;
}
}
}

  • Dagger2 生成了 ActivityComponent 的实现类 DaggerActivityComponent
  • DaggerActivityComponent 对象是在调用 build() 方法时进行初始化(initialize方法)
  • 初始化Provider
  • 初始化MembersInjector
  • inject 方法中,调用injectMembers() 进行注入。

总结

关于 Dagger2 的的一些概念,就介绍到这里了。啰里啰嗦的说了好多,我们来总结一下 Dagger2 到底有些什么好处呢,应该如何使用。

好处

  • 提高开发效率,不做油漆工
    Dagger2 把 new 对象和注入的工作给做了,我们只需要将精力集中在关建业务上。
    省去了单例的写法,也不用再操心单例线程安全的问题。

  • 更方便的管理类实例
    每个app中的ApplicationComponent管理整个app的全局类实例,他们的生命周期和app的生命周期一样。
    每个页面的Component管理自己页面所以来的类实例。
    Component,Module让整个类实例结构变得很清晰。

  • 解耦
    正常情况下,new 关键字到处都有,一旦类的构造函数发生变化,哭死的心都有。。。设计模式中提倡把容易变化的部分封装起来
    Dagger2 通过 @Inject 注解 和 @Module 来创建实例
    @Inject构造函数变化时,我们不用做任何修改
    @Module 管理的实例,我们也只需要修改Module,比如HttpModule。

使用

  • 依赖注入的流程
  1. 查找Module中是否存在创建该类实例的方法
  2. 存在创建类实例的方法,查看该方法是否需要参数
    2.1 需要,则从步骤1开始,初始化每个参数
    2.2 不需要,直接初始化类实例,一次依赖注入完成
  3. 若不存在创建类方法,则查找Inject注解的构造函数,查看是否需要参数
    3.1 需要,从步骤1开始一次初始化每个参数
    3.2 不需要,初始化该实例,一次依赖注入完成
  • 使用注意事项
  • 一个app必须有一个Component来管理整个app的全局类实例
  • 多个页面可以共享一个Component
  • 最好使用自定义Scope,增强代码阅读性,及早的暴露问题(编译时检查)。

author:@ygwang